// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// MSVC++ requires this to be set before any other includes to get M_PI.
#define _USE_MATH_DEFINES

#include "media/base/audio_converter.h"

#include <stddef.h>

#include <cmath>
#include <memory>

#include "base/macros.h"
#include "base/memory/scoped_vector.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/fake_audio_render_callback.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

// Parameters which control the many input case tests.
static const int kConvertInputs = 8;
static const int kConvertCycles = 3;

// Parameters used for testing.
static const int kBitsPerChannel = 32;
static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
static const int kHighLatencyBufferSize = 2048;
static const int kLowLatencyBufferSize = 256;
static const int kSampleRate = 48000;

// Number of full sine wave cycles for each Render() call.
static const int kSineCycles = 4;

// Tuple of <input rate, output rate, output channel layout, epsilon>.
typedef std::tr1::tuple<int, int, ChannelLayout, double> AudioConverterTestData;
class AudioConverterTest
    : public testing::TestWithParam<AudioConverterTestData> {
public:
    AudioConverterTest()
        : epsilon_(std::tr1::get<3>(GetParam()))
    {
        // Create input and output parameters based on test parameters.
        input_parameters_ = AudioParameters(
            AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout,
            std::tr1::get<0>(GetParam()), kBitsPerChannel, kHighLatencyBufferSize);
        output_parameters_ = AudioParameters(
            AudioParameters::AUDIO_PCM_LOW_LATENCY, std::tr1::get<2>(GetParam()),
            std::tr1::get<1>(GetParam()), 16, kLowLatencyBufferSize);

        converter_.reset(new AudioConverter(
            input_parameters_, output_parameters_, false));

        audio_bus_ = AudioBus::Create(output_parameters_);
        expected_audio_bus_ = AudioBus::Create(output_parameters_);

        // Allocate one callback for generating expected results.
        double step = kSineCycles / static_cast<double>(output_parameters_.frames_per_buffer());
        expected_callback_.reset(new FakeAudioRenderCallback(step, kSampleRate));
    }

    // Creates |count| input callbacks to be used for conversion testing.
    void InitializeInputs(int count)
    {
        // Setup FakeAudioRenderCallback step to compensate for resampling.
        double scale_factor = input_parameters_.sample_rate() / static_cast<double>(output_parameters_.sample_rate());
        double step = kSineCycles / (scale_factor * static_cast<double>(output_parameters_.frames_per_buffer()));

        for (int i = 0; i < count; ++i) {
            fake_callbacks_.push_back(new FakeAudioRenderCallback(step, kSampleRate));
            converter_->AddInput(fake_callbacks_[i]);
        }
    }

    // Resets all input callbacks to a pristine state.
    void Reset()
    {
        converter_->Reset();
        for (size_t i = 0; i < fake_callbacks_.size(); ++i)
            fake_callbacks_[i]->reset();
        expected_callback_->reset();
    }

    // Sets the volume on all input callbacks to |volume|.
    void SetVolume(float volume)
    {
        for (size_t i = 0; i < fake_callbacks_.size(); ++i)
            fake_callbacks_[i]->set_volume(volume);
    }

    // Validates audio data between |audio_bus_| and |expected_audio_bus_| from
    // |index|..|frames| after |scale| is applied to the expected audio data.
    bool ValidateAudioData(int index, int frames, float scale)
    {
        for (int i = 0; i < audio_bus_->channels(); ++i) {
            for (int j = index; j < frames; ++j) {
                double error = fabs(audio_bus_->channel(i)[j] - expected_audio_bus_->channel(i)[j] * scale);
                if (error > epsilon_) {
                    EXPECT_NEAR(expected_audio_bus_->channel(i)[j] * scale,
                        audio_bus_->channel(i)[j], epsilon_)
                        << " i=" << i << ", j=" << j;
                    return false;
                }
            }
        }
        return true;
    }

    // Runs a single Convert() stage, fills |expected_audio_bus_| appropriately,
    // and validates equality with |audio_bus_| after |scale| is applied.
    bool RenderAndValidateAudioData(float scale)
    {
        // Render actual audio data.
        converter_->Convert(audio_bus_.get());

        // Render expected audio data.
        expected_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
            expected_audio_bus_.get());

        // Zero out unused channels in the expected AudioBus just as AudioConverter
        // would during channel mixing.
        for (int i = input_parameters_.channels();
             i < output_parameters_.channels(); ++i) {
            memset(expected_audio_bus_->channel(i), 0,
                audio_bus_->frames() * sizeof(*audio_bus_->channel(i)));
        }

        return ValidateAudioData(0, audio_bus_->frames(), scale);
    }

    // Fills |audio_bus_| fully with |value|.
    void FillAudioData(float value)
    {
        for (int i = 0; i < audio_bus_->channels(); ++i) {
            std::fill(audio_bus_->channel(i),
                audio_bus_->channel(i) + audio_bus_->frames(), value);
        }
    }

    // Verifies converter output with a |inputs| number of transform inputs.
    void RunTest(int inputs)
    {
        InitializeInputs(inputs);

        SetVolume(0);
        for (int i = 0; i < kConvertCycles; ++i)
            ASSERT_TRUE(RenderAndValidateAudioData(0));

        Reset();

        // Set a different volume for each input and verify the results.
        float total_scale = 0;
        for (size_t i = 0; i < fake_callbacks_.size(); ++i) {
            float volume = static_cast<float>(i) / fake_callbacks_.size();
            total_scale += volume;
            fake_callbacks_[i]->set_volume(volume);
        }
        for (int i = 0; i < kConvertCycles; ++i)
            ASSERT_TRUE(RenderAndValidateAudioData(total_scale));

        Reset();

        // Remove every other input.
        for (size_t i = 1; i < fake_callbacks_.size(); i += 2)
            converter_->RemoveInput(fake_callbacks_[i]);

        SetVolume(1);
        float scale = inputs > 1 ? inputs / 2.0f : inputs;
        for (int i = 0; i < kConvertCycles; ++i)
            ASSERT_TRUE(RenderAndValidateAudioData(scale));
    }

protected:
    virtual ~AudioConverterTest() { }

    // Converter under test.
    std::unique_ptr<AudioConverter> converter_;

    // Input and output parameters used for AudioConverter construction.
    AudioParameters input_parameters_;
    AudioParameters output_parameters_;

    // Destination AudioBus for AudioConverter output.
    std::unique_ptr<AudioBus> audio_bus_;

    // AudioBus containing expected results for comparison with |audio_bus_|.
    std::unique_ptr<AudioBus> expected_audio_bus_;

    // Vector of all input callbacks used to drive AudioConverter::Convert().
    ScopedVector<FakeAudioRenderCallback> fake_callbacks_;

    // Parallel input callback which generates the expected output.
    std::unique_ptr<FakeAudioRenderCallback> expected_callback_;

    // Epsilon value with which to perform comparisons between |audio_bus_| and
    // |expected_audio_bus_|.
    double epsilon_;

    DISALLOW_COPY_AND_ASSIGN(AudioConverterTest);
};

// Ensure the buffer delay provided by AudioConverter is accurate.
TEST(AudioConverterTest, AudioDelayAndDiscreteChannelCount)
{
    // Choose input and output parameters such that the transform must make
    // multiple calls to fill the buffer.
    AudioParameters input_parameters(AudioParameters::AUDIO_PCM_LINEAR,
        CHANNEL_LAYOUT_DISCRETE, kSampleRate,
        kBitsPerChannel, kLowLatencyBufferSize);
    input_parameters.set_channels_for_discrete(10);
    AudioParameters output_parameters(AudioParameters::AUDIO_PCM_LINEAR,
        CHANNEL_LAYOUT_DISCRETE, kSampleRate * 2,
        kBitsPerChannel, kHighLatencyBufferSize);
    output_parameters.set_channels_for_discrete(5);

    AudioConverter converter(input_parameters, output_parameters, false);
    FakeAudioRenderCallback callback(0.2, kSampleRate);
    std::unique_ptr<AudioBus> audio_bus = AudioBus::Create(output_parameters);
    converter.AddInput(&callback);
    converter.Convert(audio_bus.get());

    // double input_sample_rate = input_parameters.sample_rate();
    // int fill_count =
    //     (output_parameters.frames_per_buffer() * input_sample_rate /
    //      output_parameters.sample_rate()) /
    //      input_parameters.frames_per_buffer();
    //
    // This magic number is the accumulated MultiChannelResampler delay after
    // |fill_count| (4) callbacks to provide input. The number of frames delayed
    // is an implementation detail of the SincResampler chunk size (480 for the
    // first two callbacks, 512 for the last two callbacks). See
    // SincResampler.ChunkSize().
    int kExpectedDelay = 992;
    auto expected_delay = AudioTimestampHelper::FramesToTime(kExpectedDelay, kSampleRate);
    EXPECT_EQ(expected_delay, callback.last_delay());
    EXPECT_EQ(input_parameters.channels(), callback.last_channel_count());
}

TEST_P(AudioConverterTest, ArbitraryOutputRequestSize)
{
    // Resize output bus to be half of |output_parameters_|'s frames_per_buffer().
    audio_bus_ = AudioBus::Create(output_parameters_.channels(),
        output_parameters_.frames_per_buffer() / 2);
    RunTest(1);
}

TEST_P(AudioConverterTest, NoInputs)
{
    FillAudioData(1.0f);
    EXPECT_TRUE(RenderAndValidateAudioData(0.0f));
}

TEST_P(AudioConverterTest, OneInput)
{
    RunTest(1);
}

TEST_P(AudioConverterTest, ManyInputs)
{
    RunTest(kConvertInputs);
}

INSTANTIATE_TEST_CASE_P(
    AudioConverterTest, AudioConverterTest, testing::Values(
                                                // No resampling. No channel mixing.
                                                std::tr1::make_tuple(44100, 44100, CHANNEL_LAYOUT_STEREO, 0.00000048),

                                                // Upsampling. Channel upmixing.
                                                std::tr1::make_tuple(44100, 48000, CHANNEL_LAYOUT_QUAD, 0.033),

                                                // Downsampling. Channel downmixing.
                                                std::tr1::make_tuple(48000, 41000, CHANNEL_LAYOUT_MONO, 0.042)));

} // namespace media
